

class _SavingTime:
    def __init__(self, minutes_per_file):
        self.minutes_per_file = minutes_per_file
        self._next_time = None

    def saving_time(self):
        if self._next_time is None:
            self._next_time = self._first_saving_time()
        return self._next_time

    def last_save_time(self):
        next_save_time = self.saving_time()
        last_save_time = next_save_time - self.minutes_per_file*60
        return last_save_time

    def has_saved(self):
        while time.time() > self._next_time:
            self._next_time += 60 * self.minutes_per_file

    def _first_saving_time(self):
        if self.minutes_per_file >= 24 * 60:
            now = datetime.datetime.now()
            next_time = datetime.datetime(now.year, now.month, now.day) + datetime.timedelta(minutes=self.minutes_per_file)
            return next_time.timestamp()

        ts = int(time.time()) // 60 * 60
        dt = datetime.datetime.fromtimestamp(ts)
        m = int(dt.strftime('%M'))
        next_m = (m//self.minutes_per_file + 1) * self.minutes_per_file

        lack_m = next_m - m
        next_dt = dt + datetime.timedelta(minutes=lack_m)

        return next_dt.timestamp()


class LineLog:
    _is_windows = platform.system() == 'Windows'
    _Save = '_Save'

    def __init__(self, name, fields, path=None, file_minutes=60, history_hours=24):
        if path is None:
            path = pathlib.Path(__file__).parent.parent.parent.joinpath('logs')
            path = path.as_posix()

        self.name, self.path = name, path
        self.keep_history_hours = history_hours

        self._saving_time = _SavingTime(file_minutes)
        self._fields = ['time'] + fields

        self._f = self._new_f()
        self._csv = self._new_csv(self._f)
        self._lock = Lock()

        self._queue = Queue()
        self._start()
        Timer2(self._del_outdated, 3600).start()

    def log_line(self, line):
        self._queue.put(line)

    def save(self):
        # with self._lock:
        #     self._f.flush()
        self._queue.put(self._Save)

    def _start(self):
        Thread(target=self._run, daemon=True).start()

    def _run(self):
        while True:
            line = self._queue.get()
            if line == self._Save:
                self._f.flush()
                continue

            try:
                with self._lock:
                    self._check_time()
                    line.insert(0, self._time())
                    self._csv.writerow(line)
            except:
                catch_exception()

    def _format_file_name(self, ts):
        dt = datetime.datetime.fromtimestamp(ts)
        index = dt.strftime('%m.%d-%H.%M')
        file = '{}/{}-{}.csv'.format(self.path, self.name, index)
        return file

    def _new_f(self):
        file_name = self._format_file_name(self._saving_time.last_save_time())

        if self._is_windows:
            f = open(file_name, 'w', newline='')
        else:
            f = open(file_name, 'w')
        return f

    def _new_csv(self, f):
        new_csv = csv.writer(f)
        new_csv.writerow(self._fields)
        return new_csv

    def _check_time(self):
        if time.time() > self._saving_time.saving_time():
            self._f.close()
            self._saving_time.has_saved()
            self._f = self._new_f()
            self._csv = self._new_csv(self._f)

    def _time(self):
        now = time.strftime('%H:%M:%S')
        ms = '{:.3f}'.format(time.time() % 1)
        # ts = now + ms[1:]
        t = f'{now}|{ms[2:]}'
        return t

    def _del_outdated(self):
        now_dt = datetime.datetime.now()
        cut_dt = now_dt - datetime.timedelta(hours=self.keep_history_hours)

        cut_date_time = cut_dt.strftime('%m.%d-%H.%M')

        del_files = []
        all_files = glob.glob(self.path + '/{}*.csv'.format(self.name))

        year_leap = now_dt.year != cut_dt.year
        within_month = now_dt.month == cut_dt.month
        month = now_dt.strftime('%m')

        for file in all_files:
            splits = file.split('-')
            data_time_i = '{}-{}'.format(splits[-2], splits[-1].replace('.csv', ''))
            if data_time_i < cut_date_time:
                del_files.append(file)
            elif year_leap and data_time_i.startswith('12'):
                del_files.append(file)
            elif within_month and (not data_time_i.startswith(str(month))):
                del_files.append(file)

        for file in del_files:
            try:
                os.remove(file)
            except PermissionError:
                pass


class LineLog2:
    def __init__(self, name, fields, path=None, file_minutes=60, history_hours=24):
        if path is None:
            path = get_logs_path()
        self._line_log = LineLog(name, fields, path, file_minutes, history_hours)
        self._name_to_col = {f: i for i, f in enumerate(fields)}
        self._col_to_name = {v: k for k, v in self._name_to_col.items()}
        self._ache_value = {}
        self._len = len(fields)

    def log_line(self, line):
        self._line_log.log_line(line)

    def log_line_with_ache(self, line):  # do not change ache even if different
        this = [''] * len(self._name_to_col)
        for n, v in self._ache_value.items():
            i = self._name_to_col[n]
            this[i] = v

        for i, v in enumerate(line):
            if v != '' and v != ' ':
                this[i] = v

        self._line_log.log_line(this)

    def log_value(self, name, value):
        name_to_col = self._name_to_col
        line = [' '] * self._len

        for n, v in self._ache_value.items():
            i = name_to_col[n]
            line[i] = v

        line[name_to_col[name]] = value
        self._line_log.log_line(line)

    def log_value_dict(self, dic):
        name_to_col = self._name_to_col
        line = [''] * self._len

        for n, v in self._ache_value.items():
            i = name_to_col[n]
            line[i] = v

        for n, v in dic.items():
            line[name_to_col[n]] = v
        self._line_log.log_line(line)

    def log_ache(self, name, value):
        self._ache_value[name] = value

    def log_ache_dic(self, dic):
        self._ache_value.update(dic)

    def flush_ache(self):
        name_to_col = self._name_to_col
        line = [' '] * self._len

        for n, v in self._ache_value.items():
            i = name_to_col[n]
            line[i] = v
        self._line_log.log_line(line)
        return line

    def clear_ache(self):
        self._ache_value.clear()

    def save(self):
        self._line_log.save()

